package com.director.core;

import com.director.core.json.JsonParser;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.RequestContext;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.List;

/**
 * Author: Simone Ricciardi
 * Date: 26-dic-2009
 * Time: 16.26.35
 */
abstract class RequestHandler {

   public static final RequestHandler FORM_UPLOAD_HANDLER = new FormUploadRequestHandler();
   public static final RequestHandler FORM_HANDLER = new FormRequestHandler();
   public static final RequestHandler JSON_HANDLER = new JsonRequestHandler();

   /**
    * Static factory method to provide correct request handler implementation for the current
    * HTTP request.
    *
    * @return the appropriate handler for the current request.
    */
   public static RequestHandler getInstance() {

      HttpServletRequest request = DirectContext.get().getRequest();

      String contentType = request.getContentType();
      String contentTypeLowercase = "";
      if(contentType != null) {
         contentTypeLowercase = contentType.toLowerCase();
      }

      if(contentTypeLowercase.contains("multipart/form-data")) {
         return FORM_UPLOAD_HANDLER;
      } else if(contentTypeLowercase.startsWith("application/x-www-form-urlencoded") &&
            request.getMethod().toLowerCase().equals("post")) {
         return FORM_HANDLER;
      } else {
         return JSON_HANDLER;
      }
   }

   /**
    * The real implementation needs to parse the HTTP request to extract encoded transactions that will be
    * processed.
    *
    * @return a list of direct transactions.
    * @throws DirectException
    */
   abstract DirectTransaction[] parse() throws DirectException;

   /**
    * The real implementation needs to format output encoding all the events raised after transactions process.
    *
    * @return a string with the encoded events.
    */
   abstract String format();

   /**
    * Request handler for direct JSON-encoded row HTTP post.
    */
   private static class JsonRequestHandler extends RequestHandler {

      public DirectTransaction[] parse() throws DirectException {

         HttpServletRequest request = DirectContext.get().getRequest();

         String line;
         String transactionString = "";
         try {
            BufferedReader br = request.getReader();
            while((line = br.readLine()) != null) {
               transactionString += line;
            }

            transactionString = transactionString.trim();
            transactionString = transactionString.startsWith("{") ? "[" + transactionString + "]" : transactionString;

            JsonParser jsonParser = DirectContext.get().getConfiguration().getParser();
            return jsonParser.parse(transactionString, DirectTransaction[].class);

         } catch(Exception e) {
            throw new DirectException("Error while parse transaction from request", e);
         }
      }

      public String format() {

         List<DirectEvent> events = DirectContext.get().getEvents();

         DirectConfiguration configuration = DirectContext.get().getConfiguration();
         JsonParser jsonParser = configuration.getParser();
         String json = jsonParser.format(events);
         if(configuration.isPreventScriptHijacking()) {
            String prefix = configuration.getWrapPrefix();
            String suffix = configuration.getWrapSuffix();
            json = prefix + json + suffix;
         }

         return json;
      }
   }

   /**
    * Request handler for direct form post.
    * <p/>
    * Author: Simone Ricciardi
    * Date: 26-dic-2009
    * Time: 16.29.06
    */
   public static class FormRequestHandler extends RequestHandler {

      private static final String TID_PARAM = "extTID";
      private static final String ACTION_PARAM = "extAction";
      private static final String METHOD_PARAM = "extMethod";
      private static final String TYPE_PARAM = "extType";

      @Override
      public DirectTransaction[] parse() throws DirectException {

         HttpServletRequest request = DirectContext.get().getRequest();
         DirectTransaction transaction = new DirectTransaction();

         FormTransactionData data = new FormTransactionData();
         transaction.setData(data);

         Enumeration<String> paramNames = request.getParameterNames();
         while(paramNames.hasMoreElements()) {
            String paramName = paramNames.nextElement();
            String paramValue = request.getParameter(paramName);
            if(TID_PARAM.equals(paramName)) {
               transaction.setTid(Integer.parseInt(paramValue));
            } else if(ACTION_PARAM.equals(paramName)) {
               transaction.setAction(paramValue);
            } else if(METHOD_PARAM.equals(paramName)) {
               transaction.setMethod(paramValue);
            } else if(TYPE_PARAM.equals(paramName)) {
               transaction.setType(paramValue);
            } else {
               data.put(paramName, paramValue);
            }
         }

         return new DirectTransaction[]{transaction};
      }

      @Override
      public String format() {
         List<DirectEvent> events = DirectContext.get().getEvents();
         JsonParser jsonParser = DirectContext.get().getConfiguration().getParser();
         StringBuilder buffer = new StringBuilder("<html><body><textarea>");
         buffer.append(jsonParser.format(events));
         buffer.append("</textarea></body></html>");
         return buffer.toString();
      }
   }

   /**
    * Request handler for direct form post with upload data.
    */
   public static class FormUploadRequestHandler extends FormRequestHandler {

      @Override
      public DirectTransaction[] parse() throws DirectException {

         HttpServletRequest request = DirectContext.get().getRequest();
         DirectTransaction transaction = new DirectTransaction();

         FormTransactionData data = new FormTransactionData();
         transaction.setData(data);

         try {
            for(FileItem item : this.parseRequest(request)) {
               Object fieldData;
               if(item.isFormField()) {
                  fieldData = this.processNormalFormField(item, request.getCharacterEncoding());
               } else {
                  fieldData = this.processFileField(item);
               }
               if(fieldData != null) {
                  data.put(item.getFieldName(), fieldData);
               }
            }

            return new DirectTransaction[]{transaction};
         } catch(Exception e) {
            throw new DirectException(e);
         }
      }

      @SuppressWarnings("unchecked")
      private List<FileItem> parseRequest(HttpServletRequest servletRequest) throws FileUploadException {
         DiskFileItemFactory fac = this.createDiskFileItemFactory();
         ServletFileUpload upload = new ServletFileUpload(fac);
         upload.setSizeMax(DirectContext.get().getConfiguration().getUploadMaxSize());
         return upload.parseRequest(this.createRequestContext(servletRequest));
      }

      private DiskFileItemFactory createDiskFileItemFactory() {
         DiskFileItemFactory fac = new DiskFileItemFactory();
         fac.setSizeThreshold(0);
         String saveDir = DirectContext.get().getConfiguration().getUploadDir();
         if(saveDir != null) {
            fac.setRepository(new File(saveDir));
         }
         return fac;
      }

      private RequestContext createRequestContext(final HttpServletRequest req) {
         return new RequestContext() {

            public String getCharacterEncoding() {
               return req.getCharacterEncoding();
            }

            public String getContentType() {
               return req.getContentType();
            }

            public int getContentLength() {
               return req.getContentLength();
            }

            public InputStream getInputStream() throws IOException {
               InputStream in = req.getInputStream();
               if(in == null) {
                  throw new IOException("Missing content in the request");
               }
               return req.getInputStream();
            }
         };
      }

      private String processNormalFormField(FileItem item, String charset) throws UnsupportedEncodingException {
         if(charset != null) {
            return item.getString(charset);
         } else {
            return item.getString();
         }
      }

      private File processFileField(FileItem item) {
         if(item.getName() == null || item.getName().trim().length() < 1) {
            return null;
         }
         return ((DiskFileItem) item).getStoreLocation();
      }
   }
}